package com.hk.commons.poi.excel.read.sax.v03;

import com.hk.commons.poi.excel.exception.InvalidCellReadableExcelException;
import com.hk.commons.poi.excel.metadata.*;
import com.hk.commons.poi.excel.read.sax.SaxAbstractExcelReadExecutor;
import com.hk.commons.poi.excel.read.sax.v03.handler.*;
import com.hk.commons.util.CollectionUtils;
import lombok.SneakyThrows;
import org.apache.poi.hssf.eventusermodel.*;
import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord;
import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.*;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;


/**
 * Sax xls 解析
 *
 * @author Kevin
 */
public class SaxXlsExcelReadExecutor<T> extends SaxAbstractExcelReadExecutor<T>
        implements HSSFListener, XlsRecordHandler {

    /**
     * 空单元格、空行，每行最后一个单元格的sid，这三种类型的sid都 为-1,特殊处理
     *
     * @see org.apache.poi.hssf.eventusermodel.dummyrecord.DummyRecordBase#getSid()
     */
    private static final short DUMMY_RECORD_SID = -1;

    private static final Map<Short, XlsRecordHandler> DEFAULT_RECORD_HANDLER = new HashMap<>();

    private final Map<Short, XlsRecordHandler> recordHandler = new HashMap<>();

    private XlsReadContext xlsReadContext;

    private final POIFSFileSystem poifsFileSystem;

    private SheetData<T> sheetData = new SheetData<>();

    private final ReadResult<T> result = new ReadResult<>();

    static {
        DEFAULT_RECORD_HANDLER.put(BlankRecord.sid, new BlankReaderHandler());
        DEFAULT_RECORD_HANDLER.put(BoundSheetRecord.sid, new BoundSheetRecordHandler());
        DEFAULT_RECORD_HANDLER.put(FormulaRecord.sid, new FormulaRecordHandler());
        DEFAULT_RECORD_HANDLER.put(LabelRecord.sid, new LabelRecordHandler());
        DEFAULT_RECORD_HANDLER.put(LabelSSTRecord.sid, new LabelSSTRecordHandler());
        DEFAULT_RECORD_HANDLER.put(NumberRecord.sid, new NumberRecordHandler());
        DEFAULT_RECORD_HANDLER.put(SSTRecord.sid, new SstRecordHandler());
        DEFAULT_RECORD_HANDLER.put(StringRecord.sid, new StringRecordHandler());
        DEFAULT_RECORD_HANDLER.put(BOFRecord.sid, new BOFRecordHandler());
    }

    public SaxXlsExcelReadExecutor(ReadWorkbook readWorkbook,
                                   POIFSFileSystem poifsFileSystem) {
        super(readWorkbook);
        this.poifsFileSystem = poifsFileSystem;
        recordHandler.putAll(DEFAULT_RECORD_HANDLER);
        recordHandler.put(EOFRecord.sid, this);
        recordHandler.put(DUMMY_RECORD_SID, this);
        recordHandler.put(IndexRecord.sid, this);

    }

    @Override
    @SneakyThrows
    public ReadResult<T> execute() {
        onStart();
        var listener = new MissingRecordAwareHSSFListener(this);
        this.xlsReadContext = new XlsReadContext();
        xlsReadContext.setFormatListener(new FormatTrackingHSSFListener(listener));
        xlsReadContext.setTitleRow(readWorkbook.getTitleRow());
        xlsReadContext.setOutputFormulaValue(readWorkbook.isOutFormulaValue());
        xlsReadContext.setParseAllSheet(readWorkbook.isParseAllSheet());
        xlsReadContext.setStartSheetIndex(readWorkbook.getStartSheetIndex());
        xlsReadContext.setMaxSheetIndex(readWorkbook.getMaxSheetIndex());
        xlsReadContext.setTitleRow(readWorkbook.getTitleRow());
        var factory = new HSSFEventFactory();// 处理xls文件流,按流循环处理每个Record
        var request = new HSSFRequest();
        if (readWorkbook.isOutFormulaValue()) {
            request.addListenerForAllRecords(xlsReadContext.getFormatListener());// 为所有Record 注册监听器，会影响一些性能
            // request.addListener(formatListener, BOFRecord.sid);
            // request.addListener(formatListener, BoundSheetRecord.sid);
            //// request.addListener(formatListener, RowRecord.sid);
            // request.addListener(formatListener, SSTRecord.sid);
            // request.addListener(formatListener, NumberRecord.sid);
            // request.addListener(formatListener, LabelSSTRecord.sid);
            // request.addListener(formatListener, BlankRecord.sid);
            // request.addListener(formatListener, BoolErrRecord.sid);
            // request.addListener(formatListener, FormulaRecord.sid);
            // request.addListener(formatListener, StringRecord.sid);
            // request.addListener(formatListener, LabelRecord.sid);
            // request.addListener(formatListener, EOFRecord.sid);
            // request.addListener(formatListener, ExtendedFormatRecord.sid);
            // request.addListener(formatListener, NoteRecord.sid);
            // request.addListener(formatListener, RKRecord.sid);
        } else {
            var recordCollectingListener = new EventWorkbookBuilder.SheetRecordCollectingListener(xlsReadContext.getFormatListener());
            xlsReadContext.setWorkbookBuildingListener(recordCollectingListener);
            request.addListenerForAllRecords(recordCollectingListener);
            // request.addListener(workbookBuildingListener, BOFRecord.sid);
            // request.addListener(workbookBuildingListener, BoundSheetRecord.sid);
            //// request.addListener(workbookBuildingListener, RowRecord.sid);
            // request.addListener(workbookBuildingListener, SSTRecord.sid);
            // request.addListener(workbookBuildingListener, NumberRecord.sid);
            // request.addListener(workbookBuildingListener, LabelSSTRecord.sid);
            // request.addListener(workbookBuildingListener, BlankRecord.sid);
            // request.addListener(workbookBuildingListener, BoolErrRecord.sid);
            // request.addListener(workbookBuildingListener, FormulaRecord.sid);
            // request.addListener(workbookBuildingListener, StringRecord.sid);
            // request.addListener(workbookBuildingListener, LabelRecord.sid);
            // request.addListener(workbookBuildingListener, EOFRecord.sid);
            // request.addListener(workbookBuildingListener, ExtendedFormatRecord.sid);
        }
        factory.processWorkbookEvents(request, poifsFileSystem);
        var errorLogs = onCompleted();
        if (CollectionUtils.isNotEmpty(errorLogs)) {
            result.addErrorLogList(errorLogs);
        }
        return result;
    }

    @Override
    public void processRecord(Record poiRecord) {
        var handler = recordHandler.get(poiRecord.getSid());
        if (Objects.nonNull(handler) && handler.support(poiRecord, this.xlsReadContext)) {
            handler.processRecord(poiRecord, this.xlsReadContext);
        }
    }

    private void processLastCellOfRowDummyRecord(LastCellOfRowDummyRecord lastCellOfRowDummyRecord,
                                                 XlsReadContext readContext) {
        var titleList = readContext.getTitleList();
        var row = lastCellOfRowDummyRecord.getRow();
        if (row >= readContext.getTitleRow()) {
            if (readContext.isTitleRow(row)) {
                if (CollectionUtils.isEmpty(titleList)) {
                    parseTitleRow(readContext);
                }
            } else if ((readWorkbook.getSheetMaxRow() <= 0 || row <= readWorkbook.getSheetMaxRow())
                    && CollectionUtils.isNotEmpty(readContext.getColumnValueMap())) {
                T data = null;
                try {
                    onRowStart(readContext.getCurrentSheetIndex(), readContext.getCurrentSheetName(), row);
                    data = readRow(readContext, row);
                    sheetData.add(data);
                } catch (InvalidCellReadableExcelException e) {
                    sheetData.addErrorLog(new ErrorLog<>(readContext.getCurrentSheetIndex(),
                            readContext.getCurrentSheetName(), row, e.getTarget(), e.getInvalidCells()));
                } finally {
                    onRowEnd(readContext.getCurrentSheetIndex(), readContext.getCurrentSheetName(),
                            row, data, readContext.getTitleList());
                }
            }
        }
        readContext.getColumnValueMap().clear();
    }

    @Override
    public void processRecord(Record poiRecord, XlsReadContext readContext) {
        if (poiRecord instanceof EOFRecord) {// 表示Workbook或一个sheet的结尾
            processEOFRecord(readContext);
        } else if (poiRecord instanceof IndexRecord indexRecord) { // 发生在 BOFRecord 之后，读取到 IndexRecord 后才能获取每个sheet最大行
            processIndexRecord(indexRecord, readContext);
        } else {
            /*
            LastCellOfRowDummyRecord 和 MissingCellDummyRecord 为什么要判断 CollectionUtils.isNotEmpty(readContext.getColumnValueMap())
                因为当一个sheet中有批注的时候，xls会再次从头开始读取批注的内容，也会读取到 LastCellOfRowDummyRecord 和 MissingCellDummyRecord
                从头读取批注时，readContext.getColumnValueMap()的长度是没有的，所以不解析
                这里我们不需要解析批注的值，如果需要解析批注的值，还需要注入
                    com.hk.commons.poi.excel.read.sax.v03.handler.NoteRecordHandler
                    com.hk.commons.poi.excel.read.sax.v03.handler.ObjRecordHandler
                    com.hk.commons.poi.excel.read.sax.v03.handler.TextObjectRecordHandler
                所以过滤。
             */
            if (poiRecord instanceof LastCellOfRowDummyRecord lastCellOfRowDummyRecord && CollectionUtils.isNotEmpty(readContext.getColumnValueMap())) {
                processLastCellOfRowDummyRecord(lastCellOfRowDummyRecord, xlsReadContext);
            } else if (poiRecord instanceof MissingCellDummyRecord missingCellDummyRecord && CollectionUtils.isNotEmpty(readContext.getColumnValueMap())) {
                xlsReadContext.getColumnValueMap().put(missingCellDummyRecord.getColumn(), null);
            }
        }
    }

    private void processIndexRecord(IndexRecord indexRecord, XlsReadContext readContext) {
        readContext.setMaxRow(indexRecord.getLastRowAdd1());// 获取最大行
        if (!readContext.ignoreSheet()) {
            var sheetRecord = readContext.getOrderBoundSheetRecords()[readContext.getCurrentSheetIndex()];
            onSheetStart(readContext.getCurrentSheetIndex(), sheetRecord.getSheetname(), readContext.getMaxRow());
        }
    }

    /**
     * 表示Workbook或一个sheet的结尾
     */
    private void processEOFRecord(XlsReadContext readContext) {
        if (readContext.isSheet()) {
            var currentSheet = readContext.getOrderBoundSheetRecords()[readContext.getCurrentSheetIndex()];
            sheetData.setSheetIndex(readContext.getCurrentSheetIndex());
            sheetData.setSheetName(currentSheet.getSheetname());
            result.addSheetData(sheetData);
            if (CollectionUtils.isNotEmpty(sheetData.getErrorLogs())) {
                result.addErrorLogList(sheetData.getErrorLogs());
            }
            cleanSheetData();
            onSheetEnd(readContext.getCurrentSheetIndex(), currentSheet.getSheetname());
        }
    }

    private void parseTitleRow(XlsReadContext context) {
        var titles = new ArrayList<Title>(context.getColumnValueMap().size());
        var columnProperties = this.readWorkbook.getColumnField();
        context.getColumnValueMap().forEach((key, value) -> titles.add(new Title(this.readWorkbook.getTitleRow(),
                key.intValue(), value, columnProperties.get(key.intValue()))));
        result.setTitleList(titles);
        context.setTitleList(titles);
    }

    private void cleanSheetData() {
        this.sheetData = new SheetData<>();
    }
}
